// The MIT License (MIT)
//
// Copyright (c) 2016 Zhipeng Jia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

'use strict';

var WORD_MASK = [ 0, 0xff, 0xffff, 0xffffff, 0xffffffff ];

function copyBytes(fromArray, fromPos, toArray, toPos, length) {
	var i;
	for (i = 0; i < length; i++) {
		toArray[toPos + i] = fromArray[fromPos + i];
	}
}

function selfCopyBytes(array, pos, offset, length) {
	var i;
	for (i = 0; i < length; i++) {
		array[pos + i] = array[pos - offset + i];
	}
}

function SnappyDecompressor(compressed) {
	this.array = compressed;
	this.pos = 0;
}

SnappyDecompressor.prototype.readUncompressedLength = function() {
	var result = 0;
	var shift = 0;
	var c, val;
	while (shift < 32 && this.pos < this.array.length) {
		c = this.array[this.pos];
		this.pos += 1;
		val = c & 0x7f;
		if ((val << shift) >>> shift !== val) {
			return -1;
		}
		result |= val << shift;
		if (c < 128) {
			return result;
		}
		shift += 7;
	}
	return -1;
};

SnappyDecompressor.prototype.uncompressToBuffer = function(outBuffer) {
	var array = this.array;
	var arrayLength = array.length;
	var pos = this.pos;
	var outPos = 0;

	var c, len, smallLen;
	var offset;

	while (pos < array.length) {
		c = array[pos];
		pos += 1;
		if ((c & 0x3) === 0) {
			// Literal
			len = (c >>> 2) + 1;
			if (len > 60) {
				if (pos + 3 >= arrayLength) {
					return false;
				}
				smallLen = len - 60;
				len = array[pos] + (array[pos + 1] << 8) + (array[pos + 2] << 16) + (array[pos + 3] << 24);
				len = (len & WORD_MASK[smallLen]) + 1;
				pos += smallLen;
			}
			if (pos + len > arrayLength) {
				return false;
			}
			copyBytes(array, pos, outBuffer, outPos, len);
			pos += len;
			outPos += len;
		} else {
			switch (c & 0x3) {
				case 1:
					len = ((c >>> 2) & 0x7) + 4;
					offset = array[pos] + ((c >>> 5) << 8);
					pos += 1;
					break;
				case 2:
					if (pos + 1 >= arrayLength) {
						return false;
					}
					len = (c >>> 2) + 1;
					offset = array[pos] + (array[pos + 1] << 8);
					pos += 2;
					break;
				case 3:
					if (pos + 3 >= arrayLength) {
						return false;
					}
					len = (c >>> 2) + 1;
					offset = array[pos] + (array[pos + 1] << 8) + (array[pos + 2] << 16) + (array[pos + 3] << 24);
					pos += 4;
					break;
				default:
					break;
			}
			if (offset === 0 || offset > outPos) {
				return false;
			}
			selfCopyBytes(outBuffer, outPos, offset, len);
			outPos += len;
		}
	}
	return true;
};

exports.SnappyDecompressor = SnappyDecompressor;
